package com.androidvizlab.bachelor.activities;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;

import com.androidvizlab.bachelor.R;
import com.androidvizlab.bachelor.Enums.SocketMessage;
import com.androidvizlab.bachelor.Interface.SocketClientSimpleCallback;
import com.androidvizlab.bachelor.Sockets.ActivitySocketClient;
import com.androidvizlab.bachelor.customcomponents.fragments.CustomDialogFragment;
import com.androidvizlab.bachelor.customcomponents.fragments.ViewResultDataFragment;
import com.androidvizlab.bachelor.customcomponents.listenersandadapters.CustomListAdapter;
import com.androidvizlab.bachelor.customcomponents.listenersandadapters.CustomListener;
import com.androidvizlab.bachelor.datamodels.Camera;
import com.androidvizlab.bachelor.datamodels.CameraGroup;
import com.androidvizlab.bachelor.datamodels.UniqueCamGroups;
import com.androidvizlab.bachelor.datamodels.VizlabInputData;
import com.androidvizlab.bachelor.datamodels.VizlabOutputData;
import com.androidvizlab.bachelor.notifications.NoticeManager;
import com.androidvizlab.bachelor.services.VizLabDataMQTTPushService;
import com.androidvizlab.bachelor.utilities.ConnectivityDetector;
import com.androidvizlab.bachelor.utilities.NumberConverter;

import android.app.NotificationManager;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;

/**
 * The main activity for viewing the result of the calibration.
 * Uses fragment with ListView. The first fragment shows a list of
 * combination of triplet CameraGroups. When an item is selected from the list a new 
 * fragment is shown listing information on specific camera information on the group.
 * 
 * @author The Hive
 */
public class ViewResultDataActivity extends FragmentActivity
implements SocketClientSimpleCallback{
	
	//Listener and Observer
	private CustomListener customListener = null; //a custom listener for events
	
	//Fragments
	private FragmentManager fragmentManager = null;
	private FragmentTransaction fragmentTransaction = null;
	private ViewResultDataFragment fragmentCamCombo = null;
	private ViewResultDataFragment fragmentCamInfo = null;
	
	//FRAGMENT TAGS
	public static final String VIEWRESULTDATA_FRAGMENT_TAGS_CAMGRP = "camgrps_frag";
	public static final String VIEWRESULTDATA_FRAGMENT_TAGS_CAMINFO = "caminfo_frag";
	
	//Dialog fragment
	private CustomDialogFragment dialogfragment = null; //a fragment that create a dialog
	
	//Bundles and fragment argument
	private Bundle extraInfo = null;
	
	//SharedPreferences
	private SharedPreferences sharedPrefs = null; //default shared preference
	
	//Network and Connectivity
	private ConnectivityDetector connectivityDetector = null; //used to detect network and device connectivity
	
	//Data
	private HashMap<String, CameraGroup> dataMap = new HashMap<String, CameraGroup>();
	private ArrayList<Object> datalist = new ArrayList<Object>();
	private ArrayList<Object> datalist1 = new ArrayList<Object>();
	
	//ListView - for displaying data
	private ListView list = null;
	
	//ListAdapter - custom adapter to handle the ListView
	private CustomListAdapter listAdapter = null; 
	
	//**** SOCKET SERVER CONNECTION AND BACKGROUND PROCESS ****//
	
	//Sockets: Client
	private ActivitySocketClient client = null;
	private Thread clientThread = null;
	
	//Socket connection settings
	private String SERVER_IP = "";
	private int PORTNR = 1330; //default
	
	//ASYNTASK - Background process
	private ConnectToSocketServerTask serverConnection = null;
	private DisconnectFromSocketServerTask serverCloseConnection = null;
	private SendDataToServerTask sendDataToServer = null;
	
	//Button
	private Button btnSendToProduction = null;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.viewresultdata);
		setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); //force Activity on landscape mode
		
		//populateDataList();
		
		//populateObjectList();
		
		initUIComponents(); //initialise UI components
				
		getDataFromPreference(); //Get data from stored preferences
		
		initFragmentComponents(); //initialises the fragment components
		
		setPreferredLanguage(); //set the preferred language by the user saved in the preferences
	}
	
	@Override
	protected void onStart() {
		super.onStart();
		
		//CONNECT TO SERVER AND RETRIEVE DATA
		connectToServer();
		
		//Retrieve the result of the calibration and view the result
		retrievedCalibrationResult();
		
		//CLEAR NOTIFICATION AND STOP DATA PUSH SERVICE
		clearDataPushNotificationAndServices();
	}
	
	@Override
	protected void onPause() {
		super.onPause();
		
	}
	
	@Override
	protected void onDestroy() {
		super.onDestroy();
		
		//DISCONNECT FROM SERVER
		disconnectFromServer();
	}
	
	/**
	 * 
	 */
	private void populateDataList(ArrayList<CameraGroup> list)
	{		
			
		for(CameraGroup grp : list)
		{
			dataMap.put(grp.getGroupName(), grp);
		}
	}
	
	private void populateObjectList()
	{
		Collection<CameraGroup> cclist = dataMap.values();
		
		for(CameraGroup cg : cclist)
		{
			datalist.add(cg);
		}
	}
	
	private synchronized void populateObjectList(ArrayList<CameraGroup> newList)
	{	
		for(CameraGroup cc : newList)
		{
			datalist.add(cc);
		}
	}
	
	/**
	 * Populates the list that will be passed on to the fragment. 
	 * @param newList a list of unique camera groups 
	 */
	private synchronized void populateObjectList1(ArrayList<UniqueCamGroups> newList)
	{	
		for(UniqueCamGroups cc : newList)
		{
			datalist.add(cc);
		}
	}
	
	/**
	 * This clears any notifications on the status bar raised by the App.
	 */
	private void clearDataPushNotificationAndServices()
	{
		//CANCEL NOTIFICATION THAT STARTED THE ACTIVITY
		NoticeManager noticeManager = new NoticeManager();
		noticeManager.setContext(getApplicationContext());
		noticeManager.clearAll();
		
		//STOP SERVICE - stops the data push service when the notification is received and opened
		stopService(new Intent(getBaseContext(),VizLabDataMQTTPushService.class));
	}
	
	/**
	 * Instantiate necessary UI component and other variables
	 */
	private void initUIComponents()
	{
		//Network and connectivity
		connectivityDetector = new ConnectivityDetector(this);
		
		//Socket client
		client = new ActivitySocketClient(); //create a new instance
		
		//Listener
		customListener = new CustomListener();
		customListener.setResultDataActivity(this);
		
		//Button
		btnSendToProduction = (Button)this.findViewById(R.id.btnSendToProduction);
		btnSendToProduction.setOnClickListener(customListener);
	}
	
	//Initialises fragment component
	/**
	 * Initialise necessary fragment components 
	 */
	private void initFragmentComponents()
	{
		//Initialise fragment components
		fragmentManager = getSupportFragmentManager();
		
		//Create CamCombination fragment
		createCamComboFragment();
	}
	
	/**
	 * Creates CamComination fragment.
	 */
	public void createCamComboFragment()
	{
		//FRAGMENT EXTRA INFO - pass id of the xml to be inflated
		extraInfo = new Bundle();
		extraInfo.putInt("layout_id", R.layout.sortedcamcombination);
		
		//CamCombination fragment
		fragmentCamCombo = new ViewResultDataFragment();
		
		fragmentCamCombo.setOnItemClickListener(customListener);
		
		//Set extra info
		fragmentCamCombo.setArguments(extraInfo);
		
		//Set List Data to be displayed
		fragmentCamCombo.setItemList(datalist1);
		
		//Start transaction
		fragmentTransaction = fragmentManager.beginTransaction();
		
		fragmentTransaction.add(R.id.viewResultDataFragmentHolder, fragmentCamCombo,
				VIEWRESULTDATA_FRAGMENT_TAGS_CAMGRP);
		
		//fragmentTransaction.addToBackStack(null);
		
		fragmentTransaction.commit();
	}
	
	/**
	 * Creates CameraInfo fragment. The parameter 'index' 
	 * specifies which item in the list need to be displayed
	 * 
	 * @param index the index of the item in the ListView
	 */
	public void createCamInfoFragment(int index)
	{
		//FRAGMENT EXTRA INFO - pass id of the xml to be inflated
		extraInfo = new Bundle();
		extraInfo.putInt("layout_id", R.layout.caminfogrp_list);
		
		fragmentCamInfo = new ViewResultDataFragment();
		
		//Set extra info
		fragmentCamInfo.setArguments(extraInfo);
		
		
		//Set List Data to be displayed
		//ArrayList<Object> objList = findCamInfoGrp(index);
		
		ArrayList<Object> objList = findCamInfoGrp1(index);
		
		fragmentCamInfo.setItemList(objList);
		
		//Start transaction
		fragmentTransaction = fragmentManager.beginTransaction();
		
		if(fragmentCamInfo != null)
		{
			fragmentTransaction.replace(R.id.viewResultDataFragmentHolder, fragmentCamInfo,
					VIEWRESULTDATA_FRAGMENT_TAGS_CAMINFO);
			fragmentTransaction.addToBackStack(null);
		}
		else
		{
			fragmentTransaction.add(R.id.viewResultDataFragmentHolder, fragmentCamInfo,
					VIEWRESULTDATA_FRAGMENT_TAGS_CAMINFO);
			fragmentTransaction.addToBackStack(null);
		}
		fragmentTransaction.commit();
	}
	
	//*** SWITCH BETWEEN THE GENERAL AND ADVANCED OPTION ***//
	
	/**
	 * Make the fragment visible by the given tag
	 * 
	 * @param tag associated with a fragment
	 */
	public void showFragment(String tag)
	{
		Fragment fragment = fragmentManager.findFragmentByTag(tag);
		
		fragmentTransaction = fragmentManager.beginTransaction();
		
		fragmentTransaction.show(fragment);
		
		fragmentTransaction.commit();
	}
	
	/**
	 * Make the fragment invisible by the given tag
	 * 
	 * @param tag associated with a fragment
	 */
	public void hideFragment(String tag)
	{
		Fragment fragment = fragmentManager.findFragmentByTag(tag);
		
		fragmentTransaction = fragmentManager.beginTransaction();
		
		fragmentTransaction.hide(fragment);
		
		fragmentTransaction.commit();
	}
	
	//HELPER METHOD - FIND CAMERA INFO GROUPS
	
	/**
	 * This is a helper method use in creating CameraInfo fragment to 
	 * display group of cameras
	 * 
	 * @param index Determines which item in the list was click
	 * @return an ArrayList<Object> which will be passed to the ArrayAdapter
	 */
	private ArrayList<Object> findCamInfoGrp(int index)
	{	
		ArrayList<Object> objlist = new ArrayList<Object>();
		
		ArrayList<Camera> ciList = ((CameraGroup)datalist.get(index)).getCameraList();
		
		for(Camera ci : ciList)
		{
			objlist.add(ci);
		}
		
		return objlist;
	} 
	
	/**
	 * This is a helper method use in creating CameraInfo fragment to 
	 * display unique group of cameras
	 * 
	 * @param index Determines which item in the list was click
	 * @return an ArrayList<Object> which will be passed to the ArrayAdapter
	 */
	private ArrayList<Object> findCamInfoGrp1(int index)
	{	
		ArrayList<Object> objlist = new ArrayList<Object>();
		
		ArrayList<CameraGroup> cgList = ((UniqueCamGroups)datalist.get(index)).getGroups();
		
		for(CameraGroup grp : cgList)
		{
			ArrayList<Camera> ciList = grp.getCameraList();
			
			for(Camera ci : ciList)
			{
				objlist.add(ci);
			}
		}
		
		return objlist;
	}
	
	//*** SHAREDPREFERENCE ***//
	
	/**
	 * This method fetch stored information in the SharedPreference
	 * associated with this application
	 */
	private void getDataFromPreference()
	{
		sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
		
		SERVER_IP = sharedPrefs.getString("serveraddress", "");
		PORTNR = NumberConverter.convertToInt(sharedPrefs.getString("portnumber", "1330"),1330);
	}
	
	//*** SOCKET CONNECTION AND DATA TRANSFER ***//
	/**
	 * Connects to the socket server using an AsyncTask that runs in the background thread.
	 */
	private void connectToServer()
	{
		if(client != null && !client.isConnected())
		{
			serverConnection = new ConnectToSocketServerTask();
			serverConnection.setClient(client);
			serverConnection.execute();
		}
	}
	
	/**
	 * Disconnects from the server by closing the socket connection and terminating the client thread.
	 */
	private void disconnectFromServer()
	{
		if(client != null && client.isConnected())
		{
			serverCloseConnection = new DisconnectFromSocketServerTask();
			serverCloseConnection.execute();
		}
	}
	
	/**
	 * Send a request to the server to retrieve the result of the
	 * calibration.
	 * 
	 * @param request a string containing the request.
	 */
	public void retrievedCalibrationResult()
	{
		//Check if the client is still connected to insure that data can be transmitted
		//if(!client.isConnected())
		//{
			//serverCloseConnection.execute();
		//}
		
		sendDataToServer = new SendDataToServerTask();
		sendDataToServer.execute(SocketMessage.GET_RESULT_DATA);
	}
	
	//*** Network and connectivity ***//
	
	/**
	 * Helper method that determines if the device is
	 * connected to a network. An alert dialog will 
	 * pop up to warn the user if there is the device
	 * is not connected.
	 */
	private void detectNetworkConnectivity()
	{
		if(connectivityDetector.isConnectedToNetworkOnline())
		{
			Toast.makeText(this, "Device is online", Toast.LENGTH_LONG).show();
		}
		else
		{
			showDialog("no_network_alert","NO NETWORK CONNECTION","Please connect to a network.");
		}
	}
	
	/**
	 * Creates and show a dialog
	 * 
	 * @param tag a tag to identify the fragment
	 * @param title the title of the dialog
	 * @param content the message on the dialog.
	 */
	public void showDialog(String tag, String title, String content)
	{
		dialogfragment = new CustomDialogFragment();
		dialogfragment.setContext(this);
		dialogfragment.setTitle(title);
		dialogfragment.setContent(content);
		dialogfragment.setOnClickListener(customListener);
		
		fragmentTransaction = fragmentManager.beginTransaction();
		
		fragmentTransaction.add(dialogfragment, tag);
		fragmentTransaction.addToBackStack(null);
		fragmentTransaction.commit();
	}
	
	//*** GETTERS AND SETTERS ***//
	private String selectedTripCamGrps = "";
	
	/**
	 * Get the name of all members of the selected
	 * Triplet camera groups
	 * @param position of the CameraGroup on the list
	 */
	public void setSelectedTripCamGrps(int position)
	{
		CameraGroup grp = (CameraGroup)datalist.get(position);
		
		StringBuilder strbuilder = new StringBuilder();
		
		int counter = 0;
		for(Camera cam : grp.getCameraList())
		{
			strbuilder.append(cam.getCamNo());
			
			if(counter == 3)
			{
				strbuilder.append(" ");
				counter = 0;
			}
			else
			{
				strbuilder.append("_");
				counter++;
			}
		}
		
		selectedTripCamGrps = strbuilder.toString();
	}
	
	/**
	 * Returns the names of the selected unique camera groups
	 * @return name of the camera group as a string
	 */
	public void setSelectedTripCamGrps1(int position)
	{
		StringBuilder strbuilder = new StringBuilder();
		
		UniqueCamGroups uniquegrps = (UniqueCamGroups)datalist.get(position);
		
		ArrayList<CameraGroup> grps = uniquegrps.getGroups();
		
		for(CameraGroup cg : grps)
		{
			strbuilder.append(cg.getGroupName().replace(":", " "));
		}
		
		selectedTripCamGrps = strbuilder.toString().trim();
		Log.i("Selected grps", selectedTripCamGrps);
	}
	
	/**
	 * Returns the names of the selected camera groups
	 * @return name of the camera group as a string
	 */
	public String getSelectedTripCamGrps()
	{
		return selectedTripCamGrps;
	}
	
	/**
	 * Updates the contents of the ListView. Clears the old data and 
	 * pass it to the fragment where the list will be update with the
	 * new content.
	 * 
	 * @param newList a new list of contents received from the server
	 */
	public void updateDataList(final ArrayList<CameraGroup> newList)
	{			
		populateObjectList(newList); //copy the content to the main data list container
		
		fragmentCamCombo.updateListContent(datalist); //pass the new list to the fragment.
		
		fragmentCamCombo.getListAdapter().notifyDataSetChanged();
		
		Log.i("viz_result_data","data received:activity");
	}
	
	/**
	 * Updates the contents of the ListView. Clears the old data and 
	 * pass it to the fragment where the list will be update with the
	 * new content.
	 * 
	 * @param newList a new list of contents received from the server
	 */
	public void updateDataList1(final ArrayList<UniqueCamGroups> newList)
	{			
		populateObjectList1(newList); //copy the content to the main data list container
		
		fragmentCamCombo.updateListContent(datalist); //pass the new list to the fragment.
		
		fragmentCamCombo.getListAdapter().notifyDataSetChanged();
		
		Log.i("viz_result_data","data received:activity");
	}
	
	/**
	 * Set language selected in the preference
	 * language option settings. 
	 */
	public void setPreferredLanguage()
	{
		Locale locale = null;
		Configuration config = null;
		
		SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
		StringBuilder builder = new StringBuilder();
		  
		builder.append(sharedPrefs.getString("supported_language", "ENGLISH"));
		  
		  if(builder.toString().equals("English"))
		  {
			  locale = new Locale("en");
              Locale.setDefault(locale);
              config = new Configuration();
              config.locale = locale;
              getBaseContext().getResources()
                      .updateConfiguration(
                              config,
                              getBaseContext().getResources()
                                      .getDisplayMetrics());
		  }
		  else if(builder.toString().equals("Norwegian"))
		  {
			  locale = new Locale("no");
              Locale.setDefault(locale);
              config = new Configuration();
              config.locale = locale;
              getBaseContext().getResources()
                      .updateConfiguration(
                              config,
                              getBaseContext().getResources()
                                      .getDisplayMetrics());
		  }
		  else
		  {
			  //TODO
		  }

	}
	
	//********* ASYNC TASKS: SOCKET CONNECTION **********//
	
	/*
	 * This section allows the activity to avoid performing 
	 * socket connection in the UI thread
	 */
	
	/**
	 * Create an asynchronous task to perform a connection setup and
	 * attempt to connect to the socket server
	 */
	private class ConnectToSocketServerTask extends AsyncTask<Void,Integer,Void>
	{
		private ActivitySocketClient client = null;
		
		/**
		 * Set up all necessary component before executing the asynctask
		 */
		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			
			clientThread = new Thread(); //create a new thread that will be use to run the ActivitySocketClient 
			
			//client.registerSimpleHandler(customListener); //register a simple callback handler
			client.registerCallbackHandler(ViewResultDataActivity.this);
			client.setServerIP(SERVER_IP); //set the server IP from the preference
			client.setPortNr(PORTNR); // set the port number from the preference
		}

		/**
		 * Performs the task in the background
		 * The task is to connect to the socket server.
		 */
		@Override
		protected Void doInBackground(Void... params) {
			
			clientThread = new Thread(client);
			
			client.initConnection(); //create a new socket and connect to the socket server
			
			clientThread.start(); //starts the client thread.
			
			return null;
		}
		
		//GETTERS AND SETTERS
		
		public ActivitySocketClient getClient() {
			return client;
		}

		public void setClient(ActivitySocketClient client) {
			this.client = client;
		}
	}
	
	/**
	 * Send data to the server via socket connection.
	 * Performs task in the background
	 *
	 */
	private class SendDataToServerTask extends AsyncTask<Object,Integer,Void>
	{
		//Progress bar
		ProgressDialog progressDialog;
		
		@Override
		protected void onPreExecute()
		{
			//Setup progress dialog
			progressDialog = new ProgressDialog(ViewResultDataActivity.this);
			progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
			progressDialog.setMax(100);
			progressDialog.show();
		}
		
		@Override
		protected Void doInBackground(Object... params) {
			
			for(int i = 0; i < 20; i++)
			{
				publishProgress(5); //update progress
				
				try 
				{
					Thread.sleep(88);
				} 
				catch (InterruptedException e) 
				{
					e.printStackTrace();
					continue;
				}
			}
			
			progressDialog.dismiss(); //dismiss progress bar
			
			client.sendData(params[0]);
			
			return null;
		}

		@Override
		protected void onProgressUpdate(Integer... progress) {
			progressDialog.incrementProgressBy(progress[0]);
		}
	}
	
	/**
	 * Disconnect from socket server and cleanup resources
	 */
	private class DisconnectFromSocketServerTask extends AsyncTask<Void,Void,Void>
	{
		@Override
		protected Void doInBackground(Void... params) {
			client.close();
			return null;
		}
	}

	/**
	 * An implemented interface for receiving data passed on by the socket client.
	 * Must be executed on the UI thread to update the list in the fragment.
	 */
	@Override
	public void onDataReceived(Object object) {
		
		if(object instanceof VizlabOutputData)
		{
			//This is the data to be displayed once the service receives the published
			//message from the server.
			
			final VizlabOutputData data = (VizlabOutputData)object;
			
			this.runOnUiThread(new Runnable(){
				
				@Override
				public void run()
				{
					updateDataList1(data.getUniqueCamGrp());
				}
			});
		}
	}
}
